#include "Swan3.h"
#include "OCMain/zip/BZip2.h"
#include "OCMain/Log.h"
#include "VolumeGridData.h"
#include "VolumeJsonWriter.h"
#include "VolumeOptions.h"
#include "VolumeEncoder.h"

namespace OCPP
{
	namespace Volume
	{
		inline void writeBin(String fileName, Uint8Vector& data)
		{
			std::ofstream ofs(fileName, std::ios::binary);
			ofs.write((char*)&data[0], data.size() * sizeof(uint8));
		}

		inline void split(FloatVector& data,String outPath, Uint8Vector& crefData)
		{
			int dataWidth = 6200;
			int dataHeight = 4200;

			// 6200
			int tileWidth = 775; // + 2 = 777
			int tileHeight = 525; // + 2 = 527

			// 16 * 16 (387.5 * 16 = 6200)
			// 8 * 8 (775 * 8 = 6200)
			// 4 * 4 (1550 * 4 = 6200)
			// 2 * 2 (3100 * 2 = 6200)

			int level = 3;
			int tileNum = pow(2, level);

			if (crefData.size() == 0)
			{
				crefData.resize(data.size());
			}

			std::vector<Uint8Vector> lodData;
			lodData.resize(tileNum * tileNum);

			size_t offset = 0;
			for (int r = 0; r < dataHeight; r++)
			{
				int r0 = dataHeight - r - 1;
				int tileR = Math::Floor(r0 / Math::Ceil(dataHeight * 1.0 / tileNum));
				for (int c = 0; c < dataWidth; c++, offset++)
				{
					float value = data[offset];
					uint8 decValue = VolumeEncoder::encodeInRange(value, VolumeEncoder::EncodeRangeDBZ);

					// cref
					if (decValue > crefData[offset]) {
						crefData[offset] = decValue;
					}

					// lod
					int tileC0 = Math::Ceil(dataWidth * 1.0 / tileNum);
					int tileC = Math::Floor(c / tileC0);
					int lodIndex = tileR * tileNum + tileC;
					Uint8Vector& tileData = lodData[lodIndex];
					tileData.push_back(decValue);
					//uint8 nx1 = 0, ny1 = 0;
					// 
					//if (c > 0 && c < dataWidth - 1 && r > 0 && r < dataHeight - 1) {
					//	float px = data[offset + 1];
					//	float nx = data[offset - 1];
					//	float py = data[offset + dataWidth];
					//	float ny = data[offset - dataWidth];
					//	Vector2 octVec = VolumeUtil::computeNormal(value, px, nx, py, ny);
					//	nx1 = (uint8)octVec[0];
					//	ny1 = (uint8)octVec[1];
					//}
					//tileData.push_back(nx1);
					//tileData.push_back(ny1);
				}
			}

			for (int i = 0; i < lodData.size(); i++)
			{
				int r = Math::Floor(i / tileNum);
				int c = i % tileNum;
				writeBin(joinPath(outPath, str_format("%d_%d_%d.bin", level, tileNum - 1 - r, c)), lodData[i]);
			}
		}

		bool Swan3::readHeader(std::istream& sstream,String outPath, Uint8Vector& crefData)
		{
			sstream.read((char*)&header, sizeof(swan3header));
			if (sstream.gcount() != sizeof(swan3header))
			{
				return false;
			}

			String block;
			block.resize(header.blockLen);
			sstream.read((char*)block.data(), block.size());

			if (header.Compress == 1)
			{
				String unzipBlock;
				unzipBlock.resize(header.unzipBytes);
				BZip2::decpressBZ2(block, unzipBlock);
				block = unzipBlock;
			}

			int width = header.nX;
			int height = header.nY;

			if (width * height * 2 != block.size())
			{
				return false;
			}

			Int16Vector data;
			data.resize(width * height);

			memcpy((char*)&data[0], block.data(), block.size());

			float minValue = 1e+5;
			float maxValue = -minValue;

			FloatVector floatVec;
			int offset = 0;

			for (int r = 0; r < height; r++)
			{
				for (int c = 0; c < width; c++, offset++)
				{
					const auto& i16 = data[offset];
					float value = i16 / header.scale;
					if (i16 < 0) {
						value = 0.0;
					}
					floatVec.push_back(value);
					minValue = std::min(minValue, value);
					maxValue = std::max(maxValue, value);
				}
			}
			
			split(floatVec, outPath, crefData);

			return true;
		}

		bool Swan3::convert(String fileName)
		{
			return false;
		}

		bool Swan3::convert(std::istream& sstream)
		{
			return false;
		}

		bool Swan3::splitLOD(String fileName, String outPath,Uint8Vector& crefData)
		{
			String name, ext, path;
			StringUtil::splitFullFilename(fileName, name, ext, path);
			StringUtil::toLowerCase(ext);

			std::ifstream ifs(fileName, std::ios::binary);

			if (!readHeader(ifs, outPath, crefData))
			{
				return false;
			}

			return true;
		}
	}
}